package ws.performancetest.tests.thread;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import ws.utils.io.FileUtil;
import ws.performancetest.api.AbstractTestSuite;
import ws.performancetest.api.TestSuite;
import ws.performancetest.api.annotation.PerformanceTest;
import ws.performancetest.api.exception.TestSuiteSetupException;

/**
 * Test of synchronization mechanisms using a multiple thread.
 *
 * The purpose is to record the total time it takes for a set of threads
 * to obtain lock on an object, for each synchronization mechanism.
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class MultipleThreadTest extends AbstractTestSuite {

  /** Object to obtain lock. */
  private static Synchronization sync = new Synchronization();
  /**
   * Each thread will be given a custom id. This id is incremented as the number
   * of created threads increases.
   */
  private static int threadId = 0;
  /**
   * List with the created threads. It has size of {@code numberOfThreads}.
   */
  private Thread[] threadList;

  public MultipleThreadTest() throws TestSuiteSetupException {
    super("MultipleThread_Results", "VMultipleThread_Summary");
    scriptname = "multipleThread.sql";
    samplesCount = 1;
  }

  /** Test of the synchronized method for multiple threads. */
  @PerformanceTest(name="SynchronizedMethod")
  public void testSynchronizedMethod() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.synchronizedMethod();
        }
      }
    });
  }

  /** Test of the synchronized method for multiple threads, with yield. */
  @PerformanceTest(name="SynchronizedMethodYield")
  public void testSynchronizedMethodYield() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.synchronizedMethod();
          Thread.yield();
        }
      }
    });
  }

  /** Test of the synchronized block for multiple threads. */
  @PerformanceTest(name="SynchronizedBlock")
  public void testSynchronizedBlock() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.synchronizedBlock();
        }
      }
    });
  }

  /** Test of the synchronized block for multiple threads, with yield */
  @PerformanceTest(name="SynchronizedBlockYield")
  public void testSynchronizedBlockYield() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.synchronizedBlock();
          Thread.yield();
        }
      }
    });
  }

  /** Test of the ReentrantLock for multiple threads. */
  @PerformanceTest(name="Lock")
  public void testLock() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.lock();
        }
      }
    });
  }

  /** Test of the ReentrantLock for multiple threads, with yield. */
  @PerformanceTest(name="LockYield")
  public void testLockYield() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.lock();
          Thread.yield();
        }
      }
    });
  }

  /** Test of the Reentrant Read Lock for multiple threads. */
  @PerformanceTest(name="ReadLock")
  public void testReadLock() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.readLock();
        }
      }
    });
  }

  /** Test of the Reentrant Read Lock for multiple threads, with yield. */
  @PerformanceTest(name="ReadLockYield")
  public void testReadLockYield() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.readLock();
          Thread.yield();
        }
      }
    });
  }

  /** Test of the Reentrant Write Lock for multiple threads. */
  @PerformanceTest(name="WriteLock")
  public void testWriteLock() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.writeLock();
        }
      }
    });
  }

  /** Test of the Reentrant Write Lock for multiple threads, with yield. */
  @PerformanceTest(name="WriteLockYield")
  public void testWriteYield() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        for (int i = 0; i < cycleCount; ++i) {
          sync.writeLock();
          Thread.yield();
        }
      }
    });
  }

  /** Test of the Semaphore Lock for multiple threads. */
  @PerformanceTest(name="Semaphore")
  public void testSemaphore() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        try {
          for (int i = 0; i < cycleCount; ++i) {
            sync.semaphore();
          }
        } catch (InterruptedException ex) {
          ex.printStackTrace();
          System.exit(1);
        }
      }
    });
  }

  /** Test of the Semaphore Lock for multiple threads, with yield. */
  @PerformanceTest(name="SemaphoreYield")
  public void testSemaphoreYield() throws InterruptedException {
    test(new Runnable() {

      public void run() {
        try {
          for (int i = 0; i < cycleCount; ++i) {
            sync.semaphore();
            Thread.yield();
          }
        } catch (InterruptedException ex) {
          ex.printStackTrace();
          System.exit(1);
        }
      }
    });
  }

  /**
   * Run a test for a given task.
   *
   * <p>
   * A task consists of trying to obtain object lock, using several mechanisms:</br>
   * <ul>
   *  <li>Synchronized Method.
   *  <li>Synchronized Method, with yield.
   *  <li>Synchronized Block.
   *  <li>Synchronized Block, with yield.
   *  <li>ReentrantLock.
   *  <li>ReentrantLock, with yield.
   *  <li>Read lock.
   *  <li>Read lock, with yield.
   *  <li>Write lock.
   *  <li>Write lock, with yield.
   * </ul>
   * </p>
   *
   * </p>
   * <p>
   * This method also records the time it took for all threads on the thread list
   * to accomplish its task.
   * </p>
   *
   * @param r thread task.
   *
   * @throws InterruptedException
   */
  private void test(Runnable r) throws InterruptedException {
    createThreadList(r);

    /* Start the threads and record the total time it took for them to finish. */
    long t_start = System.nanoTime();
    startThreads();
    waitForThreads();
    long t_finish = System.nanoTime();

    addLog(t_finish - t_start);

    /* Write the thread commutation list to a file. */
    if (Synchronization.doLog) {
      try {
        FileUtil.appendTextToFile(sync.toString(), currentTest + "-log.txt");
        sync.clearList();
      } catch (IOException ex) {
        Logger.getLogger(MultipleThreadTest.class.getName()).log(Level.SEVERE, null, ex);
        System.exit(1);
      }
    }
  }

  /**
   * Create threads.
   *
   * @param r task that each threads runs.
   */
  private void createThreadList(Runnable r) {
    threadList = new Thread[numberOfThreads];

    for (int i = 0; i < numberOfThreads; ++i) {
      threadList[i] = new Thread(r);
      threadList[i].setName("thread #" + threadId++);
    }
  }

  /**
   * Start the threads on the thread list.
   */
  private void startThreads() {
    for (Thread t : threadList) {
      t.start();
    }
  }

  /**
   * Wait for the threads on the list to finish.
   *
   * @throws InterruptedException
   */
  private void waitForThreads() throws InterruptedException {
    for (Thread t : threadList) {
      t.join();
    }
  }

  @Override
  public String toString() {
    return "MultipleThreadTest";
  }

  public static void main(String[] args) throws Exception {
    Integer[] threadAmounts = {10, 50, 100, 250, 500};
    TestSuite test = new MultipleThreadTest();
    test.setThreadAmountList(threadAmounts);

    /* run the tests. */
    test.runAllTests();
  }
}
